#include "Kernel.h"
#include "Drivers/Console.h"
#include "Scheduler/Scheduler.h"
#include "Hal/IHardware.h"
#include "Hal/Memory.h"
#include "Hal/x86/Hardware.h"
#include "Hal/x86/CPU.h"
#include "Hal/x86/GDT.h"
#include "Hal/x86/IDT.h"
#include "Hal/x86/Timer.h"
#include "Hal/x86/Paging.h"

using namespace x86;

Hardware _sHardware;
IHardware * sHardware = &_sHardware;

Result Hardware::Initialize( void )
{
	HAL_TRACE( "x86 Initializing...\n" );

	GDT::Initialize();
	IDT::Initialize();
	Timer::Initialize( 32 );

	return RESULT_OK;
}


void Hardware::Lock( void )
{
	asm( "cli" );
	lockCount++;
}


void Hardware::Unlock( void )
{
	ASSERT( lockCount > 0 );

	if( --lockCount == 0 )
		asm( "sti" );
}


void Hardware::AddInterruptHandler( int interrupt, InterruptHandler handler )
{
	ASSERT( interrupt >= 0 && interrupt <= 255 );

	IDT::RegisterHandler( interrupt, handler );
}


ITaskContext * Hardware::CreateContext( void * stackPointer, Size stackSize, TaskEntry entry, void * param )
{
	ASSERT( stackPointer != NULL );
	ASSERT( entry != NULL );

	HAL_TRACE( "Preparing task.\n" );

	uint32 * stack = (uint32 *)( (Address)stackPointer + stackSize );
	*--stack = (uint32)param;
	*--stack = 0;
	*--stack = 0x0202;
	*--stack = 0x08;
	*--stack = (uint32)entry;
	*--stack = 0;
	*--stack = 0;

	TaskContext * context = (TaskContext *)CreateEmptyContext();

	if( context != NULL )
	{
	    Registers * regs = &context->fRegisters;

		regs->EFlags = 0x0202;
		regs->CS = 0x08;
		regs->EIP = (uint32)entry;
		regs->SS = regs->GS = regs->FS = regs->ES = regs->DS = 0x10;
		regs->ESP = (uint32)stack;
	}

#if 0
	TRACE_CONTEXT( context );
#endif

	return context;
}

ITaskContext * Hardware::CreateEmptyContext( void )
{
	TaskContext * context = new TaskContext();

    if( context == NULL )
        return NULL;

	Memory_Set( &context->fRegisters, 0, sizeof(Registers) );
    return context;
}

void Hardware::DisposeContext( ITaskContext * context )
{
	ASSERT( context != NULL );

	HAL_TRACE( "Disposing of task context.\n" );
	delete context;
}


void Hardware::YieldContext( ITaskContext * context )
{
	HAL_TRACE( "Yielding task context.\n" );
	asm( "int $0xFF" );
}


void Hardware::SwitchContext( ITaskContext * oldContext, ITaskContext * currentContext, ITaskContext * newContext )
{
	ASSERT( newContext != NULL );

	HAL_TRACE( "Switching tasks.\n" );

#if HAL_TRACE_DETAILED
    if( oldContext )
    {
        HAL_TRACE( "Old: %p...\n", oldContext );
        Hardware::DumpContext( oldContext, &sDebugger );
    }

    if( currentContext )
    {
        HAL_TRACE( "Current: %p...\n", currentContext );
        Hardware::DumpContext( currentContext, &sDebugger );
    }

    if( newContext )
    {
        HAL_TRACE( "New: %p...\n", newContext );
        Hardware::DumpContext( newContext, &sDebugger );
    }
#endif

	if( oldContext != NULL && currentContext != NULL )
		Memory_Copy( &oldContext, &currentContext, sizeof(TaskContext) );

	CPU_ContextSwitch( &((TaskContext *)newContext)->fRegisters );
}


void Hardware::DumpContext( ITaskContext * context, ITextOutput * output )
{
	ASSERT( output != NULL );

    if( context == NULL )
    {
        output->Write("Context: (null)\n" );
        return;
    }

	Registers * regs = &((TaskContext *)context)->fRegisters;

	output->Write( "\tInt: %02x\tErr: %08x\n", regs->Interrupt, regs->Error );
	output->Write( "\tEIP: %08x\tEF:  %08x\tCS:  %04x\t\tSS:  %04x\n", regs->EIP, regs->EFlags, regs->CS, regs->SS );
	output->Write( "\tDS:  %04x\t\tES:  %04x\t\tFS:  %04x\t\tGS:  %04x\n", regs->DS, regs->ES, regs->FS, regs->GS );
	output->Write( "\tEAX: %08x\tECX: %08x\tEBX: %08x\tEDX: %08x\n", regs->EAX, regs->ECX, regs->EBX, regs->EDX );
	output->Write( "\tESP: %08x\tEBP: %08x\tESI: %08x\tEDI: %08x\n", regs->ESP, regs->EBP, regs->ESI, regs->EDI );
	// output->Write( "\tCR0: %08x\tCR2: %08x\tCR3: %08x\tCR4: %08x\n", cr0(), cr2(), cr3(), cr4() );

	output->Write( "\n\tStack: %04x:%08x", regs->SS, regs->ESP );
	for( uint32 i = 1; i <= 16; i++ )
	{
		if( i % 2 == 1 )
			output->Write( '\n' );

		uint32 * stack = (uint32 *)( (Address)regs->ESP + i * 4 );
		output->Write( "\t%+2u %04x:%08x  %08x\t", (Address)stack - regs->ESP, regs->SS, (Address)stack, *stack );
	}

    output->Write( '\n' );
}


void Hardware::DumpCurrentContext( ITextOutput * output )
{
	ASSERT( output != NULL );

	static TaskContext context;
	Memory_Set( &context.fRegisters, 0, sizeof(Registers) );

	CPU_SaveContext( &context.fRegisters, 2 ); // assume they want the context before the function this was called from.

	DumpContext( &context, output );
}
